home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / Net / POP3.php < prev    next >
Encoding:
PHP Script  |  2004-10-01  |  31.8 KB  |  1,200 lines

  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | Copyright (c) 2002, Richard Heyes                                     |
  4. // | All rights reserved.                                                  |
  5. // |                                                                       |
  6. // | Redistribution and use in source and binary forms, with or without    |
  7. // | modification, are permitted provided that the following conditions    |
  8. // | are met:                                                              |
  9. // |                                                                       |
  10. // | o Redistributions of source code must retain the above copyright      |
  11. // |   notice, this list of conditions and the following disclaimer.       |
  12. // | o Redistributions in binary form must reproduce the above copyright   |
  13. // |   notice, this list of conditions and the following disclaimer in the |
  14. // |   documentation and/or other materials provided with the distribution.|
  15. // | o The names of the authors may not be used to endorse or promote      |
  16. // |   products derived from this software without specific prior written  |
  17. // |   permission.                                                         |
  18. // |                                                                       |
  19. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  20. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  21. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  22. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  23. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  24. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  25. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  26. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  27. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  28. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  29. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  30. // |                                                                       |
  31. // +-----------------------------------------------------------------------+
  32. // | Author: Richard Heyes <richard@phpguru.org>                           |
  33. // | Co-Author: Damian Fernandez Sosa <damlists@cnba.uba.ar>               |
  34. // +-----------------------------------------------------------------------+
  35. //
  36. // $Id: POP3.php,v 1.1.1.1 2004/08/27 02:21:36 damian Exp $
  37.  
  38. require_once('Net/Socket.php');
  39.  
  40.  
  41.  
  42. /**
  43. *  +----------------------------- IMPORTANT ------------------------------+
  44. *  | Usage of this class compared to native php extensions such as IMAP   |
  45. *  | is slow and may be feature deficient. If available you are STRONGLY  |
  46. *  | recommended to use the php extensions.                               |
  47. *  +----------------------------------------------------------------------+
  48. *
  49. * POP3 Access Class
  50. *
  51. * For usage see the example script
  52. */
  53.  
  54. define('NET_POP3_STATE_DISCONNECTED',  1, true);
  55. define('NET_POP3_STATE_AUTHORISATION', 2, true);
  56. define('NET_POP3_STATE_TRANSACTION',   4, true);
  57.  
  58. class Net_POP3 {
  59.  
  60.     /*
  61.     * Some basic information about the mail drop
  62.     * garnered from the STAT command
  63.     *
  64.     * @var array
  65.     */
  66.     var $_maildrop;
  67.  
  68.     /*
  69.     * Used for APOP to store the timestamp
  70.     *
  71.     * @var string
  72.     */
  73.     var $_timestamp;
  74.  
  75.     /*
  76.     * Timeout that is passed to the socket object
  77.     *
  78.     * @var integer
  79.     */
  80.     var $_timeout;
  81.  
  82.     /*
  83.     * Socket object
  84.     *
  85.     * @var object
  86.     */
  87.     var $_socket;
  88.  
  89.     /*
  90.     * Current state of the connection. Used with the
  91.     * constants defined above.
  92.     *
  93.     * @var integer
  94.     */
  95.     var $_state;
  96.  
  97.     /*
  98.     * Hostname to connect to
  99.     *
  100.     * @var string
  101.     */
  102.     var $_host;
  103.  
  104.     /*
  105.     * Port to connect to
  106.     *
  107.     * @var integer
  108.     */
  109.     var $_port;
  110.  
  111.     /**
  112.     * To allow class debuging
  113.     * @var boolean
  114.     */
  115.     var $_debug = false;
  116.  
  117.  
  118.     /**
  119.     * The auth methods this class support
  120.     * @var array
  121.     */
  122.     //var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
  123.     //Disabling DIGEST-MD5 for now
  124.     var $supportedAuthMethods=array( 'CRAM-MD5', 'APOP' , 'PLAIN' , 'LOGIN', 'USER');
  125.     //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN');
  126.     //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN');
  127.  
  128.  
  129.     /**
  130.     * The auth methods this class support
  131.     * @var array
  132.     */
  133.     var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5');
  134.  
  135.  
  136.     /**
  137.     * The capability response
  138.     * @var array
  139.     */
  140.     var $_capability;
  141.  
  142.    /*
  143.     * Constructor. Sets up the object variables, and instantiates
  144.     * the socket object.
  145.     *
  146.     */
  147.  
  148.  
  149.     function Net_POP3()
  150.     {
  151.         $this->_timestamp =  ''; // Used for APOP
  152.         $this->_maildrop  =  array();
  153.         $this->_timeout   =  3;
  154.         $this->_state     =  NET_POP3_STATE_DISCONNECTED;
  155.         $this->_socket    =& new Net_Socket();
  156.         /*
  157.         * Include the Auth_SASL package.  If the package is not available,
  158.         * we disable the authentication methods that depend upon it.
  159.         */
  160.         if ((@include_once 'Auth/SASL.php') == false) {
  161.             if($this->_debug){
  162.                 echo "AUTH_SASL NOT PRESENT!\n";
  163.             }
  164.             foreach($this->supportedSASLAuthMethods as $SASLMethod){
  165.                 $pos = array_search( $SASLMethod, $this->supportedAuthMethods );
  166.                 if($this->_debug){
  167.                     echo "DISABLING METHOD $SASLMethod\n";
  168.                 }
  169.                 unset($this->supportedAuthMethods[$pos]);
  170.             }
  171.         }
  172.  
  173.  
  174.  
  175.     }
  176.  
  177.  
  178.     /**
  179.     * Handles the errors the class can find
  180.     * on the server
  181.     *
  182.     * @access private
  183.     * @return PEAR_Error
  184.     */
  185.  
  186.     function _raiseError($msg, $code =-1)
  187.     {
  188.     include_once 'PEAR.php';
  189.     return PEAR::raiseError($msg, $code);
  190.     }
  191.  
  192.  
  193.     
  194.     /*
  195.     * Connects to the given host on the given port.
  196.     * Also looks for the timestamp in the greeting
  197.     * needed for APOP authentication
  198.     *
  199.     * @param  $host Hostname/IP address to connect to
  200.     * @param  $port Port to use to connect to on host
  201.     * @return bool  Success/Failure
  202.     */
  203.     function connect($host = 'localhost', $port = 110)
  204.     {
  205.         $this->_host = $host;
  206.         $this->_port = $port;
  207.  
  208.         $result = $this->_socket->connect($host, $port, false, $this->_timeout);
  209.         if ($result === true) {
  210.             $data = $this->_recvLn();
  211.  
  212.             if( $this->_checkResponse($data) ){
  213.             // if the response begins with '+OK' ...
  214. //            if (@substr(strtoupper($data), 0, 3) == '+OK') {
  215.                 // Check for string matching apop timestamp
  216.                 if (preg_match('/<.+@.+>/U', $data, $matches)) {
  217.                     $this->_timestamp = $matches[0];
  218.                 }
  219.                 $this->_maildrop = array();
  220.                 $this->_state    = NET_POP3_STATE_AUTHORISATION;
  221.  
  222.                 return true;
  223.             }
  224.         }
  225.  
  226.         $this->_socket->disconnect();
  227.         return false;
  228.     }
  229.  
  230.     /*
  231.     * Disconnect function. Sends the QUIT command
  232.     * and closes the socket.
  233.     *
  234.     * @return bool Success/Failure
  235.     */
  236.     function disconnect()
  237.     {
  238.         return $this->_cmdQuit();
  239.     }
  240.  
  241.     /*
  242.     * Performs the login procedure. If there is a timestamp
  243.     * stored, APOP will be tried first, then basic USER/PASS.
  244.     *
  245.     * @param  $user Username to use
  246.     * @param  $pass Password to use
  247.     * @param  $apop Whether to try APOP first
  248.     * @return mixed  true on Success/ PEAR_ERROR on error
  249.     */
  250.     function login($user, $pass, $apop = true)
  251.     {
  252.         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
  253.  
  254.             if(PEAR::isError($ret= $this->_cmdAuthenticate($user , $pass , $apop ) ) ){
  255.                 return $ret;
  256.             }
  257.             if( ! PEAR::isError($ret)){
  258.                 $this->_state = NET_POP3_STATE_TRANSACTION;
  259.                 return true;
  260.             }
  261.  
  262.         }
  263.         return $this->_raiseError('Generic login error' , 1);
  264.     }
  265.  
  266.  
  267.  
  268.     /**
  269.     * Parses the response from the capability command. Stores
  270.     * the result in $this->_capability
  271.     *
  272.     * @access private
  273.     */
  274.     function _parseCapability()
  275.     {
  276.  
  277.         if(!PEAR::isError($data = $this->_sendCmd('CAPA'))){
  278.         $data = $this->_getMultiline();
  279.         }
  280.         $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY);
  281.  
  282.  
  283.         for ($i = 0; $i < count($data); $i++) {
  284.  
  285.             $capa='';
  286.             if (preg_match('/^([a-z,\-]+)( ((.*))|$)$/i', $data[$i], $matches)) {
  287.  
  288.                 $capa=strtolower($matches[1]);
  289.                 switch ($capa) {
  290.                     case 'implementation':
  291.                         $this->_capability['implementation'] = $matches[3];
  292.                         break;
  293.                     case 'sasl':
  294.                         $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
  295.                         break;
  296.                     default :
  297.                         $this->_capability[$capa] = $matches[2];
  298.                         break;
  299.                 }
  300.             }
  301.         }
  302.     }
  303.  
  304.  
  305.  
  306.  
  307.     /**
  308.      * Returns the name of the best authentication method that the server
  309.      * has advertised.
  310.      *
  311.      * @param string if !=null,authenticate with this method ($userMethod).
  312.      *
  313.      * @return mixed    Returns a string containing the name of the best
  314.      *                  supported authentication method or a PEAR_Error object
  315.      *                  if a failure condition is encountered.
  316.      * @access private
  317.      * @since  1.0
  318.      */
  319.     function _getBestAuthMethod($userMethod = null)
  320.     {
  321.  
  322. /*
  323.        return 'USER';
  324.        return 'APOP';
  325.        return 'DIGEST-MD5';
  326.        return 'CRAM-MD5';
  327. */
  328.  
  329.  
  330.         $this->_parseCapability();
  331.  
  332.         //unset($this->_capability['sasl']);
  333.  
  334.        if( isset($this->_capability['sasl']) ){
  335.            $serverMethods=$this->_capability['sasl'];
  336.        }else{
  337.             $serverMethods=array('APOP','USER');
  338.        }
  339.  
  340.         if($userMethod !== null && $userMethod !== true ){
  341.             $methods = array();
  342.             $methods[] = $userMethod;
  343.             return $userMethod;
  344.         }else{
  345.             $methods = $this->supportedAuthMethods;
  346.         }
  347.  
  348.         if( ($methods != null) && ($serverMethods != null)){
  349.  
  350.             foreach ( $methods as $method ) {
  351.  
  352.                 if ( in_array( $method , $serverMethods ) ) {
  353.                     return $method;
  354.                 }
  355.             }
  356.             $serverMethods=implode(',' , $serverMethods );
  357.             $myMethods=implode(',' ,$this->supportedAuthMethods);
  358.             return $this->_raiseError("$method NOT supported authentication method!. This server " .
  359.                 "supports these methods: $serverMethods, but I support $myMethods");
  360.         }else{
  361.             return $this->_raiseError("This server don't support any Auth methods");
  362.         }
  363.     }
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.     /* Handles the authentication using any known method
  371.      *
  372.      * @param string The userid to authenticate as.
  373.      * @param string The password to authenticate with.
  374.      * @param string The method to use ( if $usermethod == '' then the class chooses the best method (the stronger is the best ) )
  375.      *
  376.      * @return mixed  string or PEAR_Error
  377.      *
  378.      * @access private
  379.      * @since  1.0
  380.      */
  381.     function _cmdAuthenticate($uid , $pwd , $userMethod = null )
  382.     {
  383.  
  384.  
  385.         if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) {
  386.             return $method;
  387.         }
  388.  
  389.         switch ($method) {
  390.             case 'DIGEST-MD5':
  391.                 $result = $this->_authDigest_MD5( $uid , $pwd );
  392.                 break;
  393.             case 'CRAM-MD5':
  394.                 $result = $this->_authCRAM_MD5( $uid , $pwd );
  395.                 break;
  396.             case 'LOGIN':
  397.                 $result = $this->_authLOGIN( $uid , $pwd );
  398.                 break;
  399.             case 'PLAIN':
  400.                 $result = $this->_authPLAIN( $uid , $pwd );
  401.                 break;
  402.             case 'APOP':
  403.                 $result = $this->_cmdApop( $uid , $pwd );
  404.                 // if APOP fails fallback to USER auth
  405.                 if($result===false){
  406.                 echo "APOP FAILED!!!\n";
  407.                     $result=$this->_authUSER( $uid , $pwd );
  408.                 }
  409.                 break;
  410.             case 'USER':
  411.                 $result = $this->_authUSER( $uid , $pwd );
  412.             break;
  413.  
  414.  
  415.             default :
  416.                 $result = $this->_raiseError( "$method is not a supported authentication method" );
  417.                 break;
  418.         }
  419.         return $result;
  420.     }
  421.  
  422.  
  423.  
  424.  
  425.      /* Authenticates the user using the USER-PASS method.
  426.      *
  427.      * @param string The userid to authenticate as.
  428.      * @param string The password to authenticate with.
  429.      *
  430.      * @return mixed    true on success or PEAR_Error on failure
  431.      *
  432.      * @access private
  433.      * @since  1.0
  434.      */
  435.     function _authUSER($user, $pass  )
  436.     {
  437.         if ( PEAR::isError($ret=$this->_cmdUser($user) ) ){
  438.             return $ret;
  439.         }
  440.         if ( PEAR::isError($ret=$this->_cmdPass($pass) ) ){
  441.             return $ret;
  442.         }
  443.         return true;
  444.     }
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452.  
  453.      /* Authenticates the user using the PLAIN method.
  454.      *
  455.      * @param string The userid to authenticate as.
  456.      * @param string The password to authenticate with.
  457.      *
  458.      * @return array Returns an array containing the response
  459.      *
  460.      * @access private
  461.      * @since  1.0
  462.      */
  463.     function _authPLAIN($user, $pass  )
  464.     {
  465.         $cmd=sprintf('AUTH PLAIN %s', base64_encode( chr(0) . $user . chr(0) . $pass ) );
  466.  
  467.         if ( PEAR::isError( $ret = $this->_send($cmd) ) ) {
  468.             return $ret;
  469.         }
  470.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ){
  471.             return $challenge;
  472.         }
  473.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  474.             return $ret;
  475.         }
  476.         
  477.         return true;
  478.     }
  479.  
  480.  
  481.  
  482.      /* Authenticates the user using the PLAIN method.
  483.      *
  484.      * @param string The userid to authenticate as.
  485.      * @param string The password to authenticate with.
  486.      *
  487.      * @return array Returns an array containing the response
  488.      *
  489.      * @access private
  490.      * @since  1.0
  491.      */
  492.     function _authLOGIN($user, $pass  )
  493.     {
  494.         $this->_send('AUTH LOGIN');
  495.  
  496.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  497.             return $challenge;
  498.         }
  499.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  500.             return $ret;
  501.         }
  502.  
  503.  
  504.         if ( PEAR::isError( $ret = $this->_send(sprintf('"%s"', base64_encode($user))) ) ) {
  505.             return $ret;
  506.         }
  507.  
  508.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  509.             return $challenge;
  510.         }
  511.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  512.             return $ret;
  513.         }
  514.  
  515.         if ( PEAR::isError( $ret = $this->_send(sprintf('"%s"', base64_encode($pass))) ) ) {
  516.             return $ret;
  517.         }
  518.  
  519.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  520.             return $challenge;
  521.         }
  522.         return $this->_checkResponse($ret);
  523.     }
  524.  
  525.  
  526.  
  527.  
  528.  
  529.      /* Authenticates the user using the CRAM-MD5 method.
  530.      *
  531.      * @param string The userid to authenticate as.
  532.      * @param string The password to authenticate with.
  533.      *
  534.      * @return array Returns an array containing the response
  535.      *
  536.      * @access private
  537.      * @since  1.0
  538.      */
  539.     function _authCRAM_MD5($uid, $pwd )
  540.     {
  541.         if ( PEAR::isError( $ret = $this->_send( 'AUTH CRAM-MD5' ) ) ) {
  542.             return $ret;
  543.         }
  544.  
  545.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  546.             return $challenge;
  547.         }
  548.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  549.             return $ret;
  550.         }
  551.  
  552.         // remove '+ '
  553.         
  554.         $challenge=substr($challenge,2);
  555.         
  556.         $challenge = base64_decode( $challenge );
  557.  
  558.         $cram = &Auth_SASL::factory('crammd5');
  559.         $auth_str = base64_encode( $cram->getResponse( $uid , $pwd , $challenge ) );
  560.  
  561.  
  562.         if ( PEAR::isError($error = $this->_send( $auth_str ) ) ) {
  563.             return $error;
  564.         }
  565.         if ( PEAR::isError( $ret = $this->_recvLn() ) ) {
  566.             return $ret;
  567.         }
  568.         //echo "RET:$ret\n";
  569.         return $this->_checkResponse($ret);
  570.     }
  571.  
  572.  
  573.  
  574.      /* Authenticates the user using the DIGEST-MD5 method.
  575.      *
  576.      * @param string The userid to authenticate as.
  577.      * @param string The password to authenticate with.
  578.      * @param string The efective user
  579.      *
  580.      * @return array Returns an array containing the response
  581.      *
  582.      * @access private
  583.      * @since  1.0
  584.      */
  585.     function _authDigest_MD5($uid, $pwd)
  586.     {
  587.         if ( PEAR::isError( $ret = $this->_send( 'AUTH DIGEST-MD5' ) ) ) {
  588.             return $ret;
  589.         }
  590.  
  591.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  592.             return $challenge;
  593.         }
  594.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  595.             return $ret;
  596.         }
  597.  
  598.         // remove '+ '
  599.         $challenge=substr($challenge,2);
  600.  
  601.         $challenge = base64_decode( $challenge );
  602.         $digest = &Auth_SASL::factory('digestmd5');
  603.         $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge, "localhost", "pop3" ));
  604.  
  605.         if ( PEAR::isError($error = $this->_send( $auth_str ) ) ) {
  606.             return $error;
  607.         }
  608.  
  609.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  610.             return $challenge;
  611.         }
  612.         if( PEAR::isError($ret=$this->_checkResponse($challenge) )){
  613.             return $ret;
  614.         }
  615.          /*
  616.          * We don't use the protocol's third step because POP3 doesn't allow
  617.          * subsequent authentication, so we just silently ignore it.
  618.          */
  619.  
  620.         if ( PEAR::isError( $challenge = $this->_send("\r\n") ) ) {
  621.             return $challenge ;
  622.         }
  623.         
  624.         if ( PEAR::isError( $challenge = $this->_recvLn() ) ) {
  625.             return $challenge;
  626.         }
  627.         
  628.         return $this->_checkResponse($challenge);
  629.         
  630.  
  631.     }
  632.  
  633.  
  634.  
  635.  
  636.  
  637.  
  638.  
  639.  
  640.  
  641.  
  642.     /*
  643.     * Sends the APOP command
  644.     *
  645.     * @param  $user Username to send
  646.     * @param  $pass Password to send
  647.     * @return bool Success/Failure
  648.     */
  649.     function _cmdApop($user, $pass)
  650.     {
  651.         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
  652.  
  653.             if (!empty($this->_timestamp)) {
  654.                 if(PEAR::isError($data = $this->_sendCmd('APOP ' . $user . ' ' . md5($this->_timestamp . $pass)) ) ){
  655.                     return $data;
  656.                 }
  657.                 $this->_state = NET_POP3_STATE_TRANSACTION;
  658.                 return true;
  659.             }
  660.         }
  661.         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State1');
  662.     }
  663.  
  664.  
  665.  
  666.  
  667.  
  668.  
  669.  
  670.  
  671.  
  672.  
  673.  
  674.  
  675.  
  676.  
  677.  
  678.     /*
  679.     * Returns the raw headers of the specified message.
  680.     *
  681.     * @param  $msg_id Message number
  682.     * @return mixed   Either raw headers or false on error
  683.     */
  684.     function getRawHeaders($msg_id)
  685.     {
  686.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  687.             return $this->_cmdTop($msg_id, 0);
  688.         }
  689.  
  690.         return false;
  691.     }
  692.  
  693.     /*
  694.     * Returns the  headers of the specified message in an
  695.     * associative array. Array keys are the header names, array
  696.     * values are the header values. In the case of multiple headers
  697.     * having the same names, eg Received:, the array value will be
  698.     * an indexed array of all the header values.
  699.     *
  700.     * @param  $msg_id Message number
  701.     * @return mixed   Either array of headers or false on error
  702.     */
  703.     function getParsedHeaders($msg_id)
  704.     {
  705.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  706.  
  707.             $raw_headers = rtrim($this->getRawHeaders($msg_id));
  708.  
  709.             $raw_headers = preg_replace("/\r\n[ \t]+/", ' ', $raw_headers); // Unfold headers
  710.             $raw_headers = explode("\r\n", $raw_headers);
  711.             foreach ($raw_headers as $value) {
  712.                 $name  = substr($value, 0, $pos = strpos($value, ':'));
  713.                 $value = ltrim(substr($value, $pos + 1));
  714.                 if (isset($headers[$name]) AND is_array($headers[$name])) {
  715.                     $headers[$name][] = $value;
  716.                 } elseif (isset($headers[$name])) {
  717.                     $headers[$name] = array($headers[$name], $value);
  718.                 } else {
  719.                     $headers[$name] = $value;
  720.                 }
  721.             }
  722.  
  723.             return $headers;
  724.         }
  725.  
  726.         return false;
  727.     }
  728.  
  729.     /*
  730.     * Returns the body of the message with given message number.
  731.     *
  732.     * @param  $msg_id Message number
  733.     * @return mixed   Either message body or false on error
  734.     */
  735.     function getBody($msg_id)
  736.     {
  737.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  738.             $msg = $this->_cmdRetr($msg_id);
  739.             return substr($msg, strpos($msg, "\r\n\r\n")+4);
  740.         }
  741.  
  742.         return false;
  743.     }
  744.  
  745.     /*
  746.     * Returns the entire message with given message number.
  747.     *
  748.     * @param  $msg_id Message number
  749.     * @return mixed   Either entire message or false on error
  750.     */
  751.     function getMsg($msg_id)
  752.     {
  753.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  754.             return $this->_cmdRetr($msg_id);
  755.         }
  756.  
  757.         return false;
  758.     }
  759.  
  760.     /*
  761.     * Returns the size of the maildrop
  762.     *
  763.     * @return mixed Either size of maildrop or false on error
  764.     */
  765.     function getSize()
  766.     {
  767.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  768.             if (isset($this->_maildrop['size'])) {
  769.                 return $this->_maildrop['size'];
  770.             } else {
  771.                 list(, $size) = $this->_cmdStat();
  772.                 return $size;
  773.             }
  774.         }
  775.  
  776.         return false;
  777.     }
  778.  
  779.     /*
  780.     * Returns number of messages in this maildrop
  781.     *
  782.     * @return mixed Either number of messages or false on error
  783.     */
  784.     function numMsg()
  785.     {
  786.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  787.             if (isset($this->_maildrop['num_msg'])) {
  788.                 return $this->_maildrop['num_msg'];
  789.             } else {
  790.                 list($num_msg, ) = $this->_cmdStat();
  791.                 return $num_msg;
  792.             }
  793.         }
  794.  
  795.         return false;
  796.     }
  797.  
  798.     /*
  799.     * Marks a message for deletion. Only will be deleted if the
  800.     * disconnect() method is called.
  801.     *
  802.     * @param  $msg_id Message to delete
  803.     * @return bool Success/Failure
  804.     */
  805.     function deleteMsg($msg_id)
  806.     {
  807.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  808.             return $this->_cmdDele($msg_id);
  809.         }
  810.  
  811.         return false;
  812.     }
  813.  
  814.     /*
  815.     * Combination of LIST/UIDL commands, returns an array
  816.     * of data
  817.     *
  818.     * @param  $msg_id Optional message number
  819.     * @return mixed Array of data or false on error
  820.     */
  821.     function getListing($msg_id = null)
  822.     {
  823.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  824.             if (!isset($msg_id)){
  825.                 if ($list = $this->_cmdList()) {
  826.                     if ($uidl = $this->_cmdUidl()) {
  827.                         foreach ($uidl as $i => $value) {
  828.                             $list[$i]['uidl'] = $value['uidl'];
  829.                         }
  830.                     }
  831.  
  832.                     return $list;
  833.                 }
  834.             } else {
  835.                 if ($list = $this->_cmdList($msg_id) AND $uidl = $this->_cmdUidl($msg_id)) {
  836.                     return array_merge($list, $uidl);
  837.                 }
  838.             }
  839.         }
  840.  
  841.         return false;
  842.     }
  843.  
  844.     /*
  845.     * Sends the USER command
  846.     *
  847.     * @param  $user Username to send
  848.     * @return bool  Success/Failure
  849.     */
  850.     function _cmdUser($user)
  851.     {
  852.         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
  853.             return $this->_sendCmd('USER ' . $user);
  854.         }
  855.         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
  856.     }
  857.  
  858.  
  859.     /*
  860.     * Sends the PASS command
  861.     *
  862.     * @param  $pass Password to send
  863.     * @return bool  Success/Failure
  864.     */
  865.     function _cmdPass($pass)
  866.     {
  867.         if ($this->_state == NET_POP3_STATE_AUTHORISATION) {
  868.             return $this->_sendCmd('PASS ' . $pass);
  869.         }
  870.         return $this->_raiseError('Not In NET_POP3_STATE_AUTHORISATION State');
  871.     }
  872.  
  873.  
  874.     /*
  875.     * Sends the STAT command
  876.     *
  877.     * @return mixed Indexed array of number of messages and
  878.     *               maildrop size, or false on error.
  879.     */
  880.     function _cmdStat()
  881.     {
  882.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  883.             if(!PEAR::isError($data = $this->_sendCmd('STAT'))){
  884.                 sscanf($data, '+OK %d %d', $msg_num, $size);
  885.                 $this->_maildrop['num_msg'] = $msg_num;
  886.                 $this->_maildrop['size']    = $size;
  887.  
  888.                 return array($msg_num, $size);
  889.             }
  890.         }
  891.         return false;
  892.     }
  893.  
  894.  
  895.     /*
  896.     * Sends the LIST command
  897.     *
  898.     * @param  $msg_id Optional message number
  899.     * @return mixed   Indexed array of msg_id/msg size or
  900.     *                 false on error
  901.     */
  902.     function _cmdList($msg_id = null)
  903.     {
  904.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  905.             if (!isset($msg_id)) {
  906.                 if(!PEAR::isError($data = $this->_sendCmd('LIST') )){
  907.                     $data = $this->_getMultiline();
  908.                     $data = explode("\r\n", $data);
  909.                     foreach ($data as $line) {
  910.                         sscanf($line, '%s %s', $msg_id, $size);
  911.                         $return[] = array('msg_id' => $msg_id, 'size' => $size);
  912.                     }
  913.                     return $return;
  914.                 }
  915.             } else {
  916.                 if(!PEAR::isError($data = $this->_sendCmd('LIST ' . $msg_id))){
  917.                     sscanf($data, '+OK %d %d', $msg_id, $size);
  918.                     return array('msg_id' => $msg_id, 'size' => $size);
  919.                 }
  920.             }
  921.         }
  922.  
  923.         return false;
  924.     }
  925.  
  926.  
  927.     /*
  928.     * Sends the RETR command
  929.     *
  930.     * @param  $msg_id The message number to retrieve
  931.     * @return mixed   The message or false on error
  932.     */
  933.     function _cmdRetr($msg_id)
  934.     {
  935.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  936.             if(!PEAR::isError($data = $this->_sendCmd('RETR ' . $msg_id) )){
  937.                 $data = $this->_getMultiline();
  938.                 return $data;
  939.             }
  940.         }
  941.  
  942.         return false;
  943.     }
  944.  
  945.  
  946.     /*
  947.     * Sends the DELE command
  948.     *
  949.     * @param  $msg_id Message number to mark as deleted
  950.     * @return bool Success/Failure
  951.     */
  952.     function _cmdDele($msg_id)
  953.     {
  954.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  955.             return $this->_sendCmd('DELE ' . $msg_id);
  956.         }
  957.  
  958.         return false;
  959.     }
  960.  
  961.  
  962.     /*
  963.     * Sends the NOOP command
  964.     *
  965.     * @return bool Success/Failure
  966.     */
  967.     function _cmdNoop()
  968.     {
  969.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  970.             if(!PEAR::isError($data = $this->_sendCmd('NOOP'))){
  971.                 return true;
  972.             }
  973.         }
  974.  
  975.         return false;
  976.     }
  977.  
  978.     /*
  979.     * Sends the RSET command
  980.     *
  981.     * @return bool Success/Failure
  982.     */
  983.     function _cmdRset()
  984.     {
  985.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  986.             if(!PEAR::isError($data = $this->_sendCmd('RSET'))){
  987.                 return true;
  988.             }
  989.         }
  990.  
  991.         return false;
  992.     }
  993.  
  994.     /*
  995.     * Sends the QUIT command
  996.     *
  997.     * @return bool Success/Failure
  998.     */
  999.     function _cmdQuit()
  1000.     {
  1001.         $data = $this->_sendCmd('QUIT');
  1002.         $this->_state = NET_POP3_STATE_DISCONNECTED;
  1003.         $this->_socket->disconnect();
  1004.  
  1005.         return (bool)$data;
  1006.     }
  1007.  
  1008.  
  1009.     /*
  1010.     * Sends the TOP command
  1011.     *
  1012.     * @param  $msg_id    Message number
  1013.     * @param  $num_lines Number of lines to retrieve
  1014.     * @return mixed Message data or false on error
  1015.     */
  1016.     function _cmdTop($msg_id, $num_lines)
  1017.     {
  1018.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  1019.  
  1020.             if(!PEAR::isError($data = $this->_sendCmd('TOP ' . $msg_id . ' ' . $num_lines))){
  1021.                 return $this->_getMultiline();
  1022.             }
  1023.         }
  1024.  
  1025.         return false;
  1026.     }
  1027.  
  1028.     /*
  1029.     * Sends the UIDL command
  1030.     *
  1031.     * @param  $msg_id Message number
  1032.     * @return mixed indexed array of msg_id/uidl or false on error
  1033.     */
  1034.     function _cmdUidl($msg_id = null)
  1035.     {
  1036.         if ($this->_state == NET_POP3_STATE_TRANSACTION) {
  1037.  
  1038.             if (!isset($msg_id)) {
  1039.                 if(!PEAR::isError($data = $this->_sendCmd('UIDL') )){
  1040.                     $data = $this->_getMultiline();
  1041.                     $data = explode("\r\n", $data);
  1042.                     foreach ($data as $line) {
  1043.                         sscanf($line, '%d %s', $msg_id, $uidl);
  1044.                         $return[] = array('msg_id' => $msg_id, 'uidl' => $uidl);
  1045.                     }
  1046.  
  1047.                     return $return;
  1048.                 }
  1049.             } else {
  1050.  
  1051.                 $data = $this->_sendCmd('UIDL ' . $msg_id);
  1052.                 sscanf($data, '+OK %d %s', $msg_id, $uidl);
  1053.                 return array('msg_id' => $msg_id, 'uidl' => $uidl);
  1054.             }
  1055.         }
  1056.  
  1057.         return false;
  1058.     }
  1059.  
  1060.  
  1061.  
  1062.  
  1063.  
  1064.  
  1065.  
  1066.  
  1067.  
  1068.     /*
  1069.     * Sends a command, checks the reponse, and
  1070.     * if good returns the reponse, other wise
  1071.     * returns false.
  1072.     *
  1073.     * @param  $cmd  Command to send (\r\n will be appended)
  1074.     * @return mixed First line of response if successful, otherwise false
  1075.     */
  1076.     function _sendCmd($cmd)
  1077.     {
  1078.         $result = $this->_send($cmd);
  1079.  
  1080.         if (!PEAR::isError($result) AND $result) {
  1081.             $data = $this->_recvLn();
  1082.             if (!PEAR::isError($data) AND strtoupper(substr($data, 0, 3)) == '+OK') {
  1083.                 return $data;
  1084.             }
  1085.         }
  1086.         return $this->_raiseError($data);
  1087.     }
  1088.  
  1089.     /*
  1090.     * Reads a multiline reponse and returns the data
  1091.     *
  1092.     * @return string The reponse.
  1093.     */
  1094.     function _getMultiline()
  1095.     {
  1096.         $data = '';
  1097.         while(!PEAR::isError($tmp = $this->_recvLn() ) ) {
  1098.             if($tmp == '.'){
  1099.                 return substr($data, 0, -2);
  1100.             }
  1101.             if (substr($tmp, 0, 2) == '..') {
  1102.                 $tmp = substr($tmp, 1);
  1103.             }
  1104.             $data .= $tmp . "\r\n";
  1105.         }
  1106.         return substr($data, 0, -2);
  1107.     }
  1108.  
  1109.  
  1110.    /**
  1111.     * Sets the bebug state
  1112.     *
  1113.     * @access public
  1114.     * @return void
  1115.     */
  1116.     function setDebug($debug=true)
  1117.     {
  1118.         $this->_debug=$debug;
  1119.     }
  1120.  
  1121.  
  1122.  
  1123.  
  1124.  
  1125.    /**
  1126.      * Send the given string of data to the server.
  1127.      *
  1128.      * @param   string  $data       The string of data to send.
  1129.      *
  1130.      * @return  mixed   True on success or a PEAR_Error object on failure.
  1131.      *
  1132.      * @access  private
  1133.      * @since   1.0
  1134.      */
  1135.     function _send($data)
  1136.     {
  1137.         if ($this->_debug) {
  1138.             echo "C: $data\n";
  1139.         }
  1140.  
  1141.         if (PEAR::isError($error = $this->_socket->writeLine($data))) {
  1142.             return $this->_raiseError('Failed to write to socket: ' . $error->getMessage());
  1143.         }
  1144.         return true;
  1145.     }
  1146.  
  1147.  
  1148.  
  1149.      /**
  1150.      * Receive the given string of data from the server.
  1151.      *
  1152.      * @return  mixed   a line of response on success or a PEAR_Error object on failure.
  1153.      *
  1154.      * @access  private
  1155.      * @since  1.0
  1156.      */
  1157.     function _recvLn()
  1158.     {
  1159.         if (PEAR::isError( $lastline = $this->_socket->readLine( 8192 ) ) ) {
  1160.             return $this->_raiseError('Failed to write to socket: ' . $this->lastline->getMessage() );
  1161.         }
  1162.         if($this->_debug){
  1163.             // S: means this data was sent by  the POP3 Server
  1164.             echo "S:$lastline\n" ;
  1165.         }
  1166.         return $lastline;
  1167.     }
  1168.  
  1169.      /**
  1170.      * Checks de server Response
  1171.      *
  1172.      * @return  mixed   true on success or a PEAR_Error object on failure.
  1173.      *
  1174.      * @access  private
  1175.      * @since  1.3.3
  1176.      */
  1177.  
  1178.     function _checkResponse($response)
  1179.     {
  1180.         if (@substr(strtoupper($response), 0, 3) == '+OK') {
  1181.             return true;
  1182.         }else{
  1183.             if (@substr(strtoupper($response), 0, 4) == '-ERR') {
  1184.                 return $this->_raiseError($response);
  1185.             }else{
  1186.                 if (@substr(strtoupper($response), 0, 2) == '+ ') {
  1187.                     return true;
  1188.                 }
  1189.             }
  1190.     
  1191.         }
  1192.         return $this->_raiseError("Unknown Response ($response)");
  1193.     }
  1194.     
  1195.  
  1196.  
  1197. }
  1198.  
  1199. ?>
  1200.